/*
 * Copyright 2022 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skgpu_graphite_DrawPassCommands_DEFINED
#define skgpu_graphite_DrawPassCommands_DEFINED

#include "include/core/SkRect.h"
#include "src/base/SkArenaAlloc.h"
#include "src/base/SkTBlockList.h"
#include "src/gpu/graphite/DrawTypes.h"

namespace skgpu::graphite {
namespace DrawPassCommands {
// A list of all the commands types used by a DrawPass.
// Each of these is reified into a struct below.
//
// The design of this systems is based on SkRecords.

// (We're using the macro-of-macro trick here to do several different things with the same list.)
//
// We leave this SKGPU_DRAW_COMMAND_TYPES macro defined for use by code that wants to operate on
// DrawPassCommands types polymorphically.
#define SKGPU_DRAW_PASS_COMMAND_TYPES(M) \
    M(BindGraphicsPipeline)              \
    M(SetBlendConstants)                 \
    M(BindUniformBuffer)                 \
    M(BindDrawBuffers)                   \
    M(BindTexturesAndSamplers)           \
    M(SetScissor)                        \
    M(Draw)                              \
    M(DrawIndexed)                       \
    M(DrawInstanced)                     \
    M(DrawIndexedInstanced)              \
    M(DrawIndirect)                      \
    M(DrawIndexedIndirect)

// Defines DrawPassCommands::Type, an enum of all draw command types.
#define ENUM(T) k##T,
enum class Type { SKGPU_DRAW_PASS_COMMAND_TYPES(ENUM) };
#undef ENUM

#define ACT_AS_PTR(ptr)   \
    operator T *() const  \
    {                     \
        return ptr;       \
    }                     \
    T *operator->() const \
    {                     \
        return ptr;       \
    }

// PODArray doesn't own the pointer's memory, and we assume the data is POD.
template <typename T> class PODArray {
public:
    PODArray() {}
    PODArray(T *ptr) : fPtr(ptr) {}
    // Default copy and assign.

    ACT_AS_PTR(fPtr)
private:
    T *fPtr;
};

#undef ACT_AS_PTR

// A macro to make it a little easier to define a struct that can be stored in DrawPass.
#define COMMAND(T, ...)                           \
    struct T {                                    \
        static constexpr Type kType = Type::k##T; \
        __VA_ARGS__;                              \
    };

COMMAND(BindGraphicsPipeline, uint32_t fPipelineIndex);
COMMAND(SetBlendConstants, PODArray<float> fBlendConstants);
COMMAND(BindUniformBuffer, BindUniformBufferInfo fInfo; UniformSlot fSlot);
COMMAND(BindDrawBuffers, BindBufferInfo fVertices; BindBufferInfo fInstances; BindBufferInfo fIndices;
    BindBufferInfo fIndirect);
COMMAND(BindTexturesAndSamplers, int fNumTexSamplers; PODArray<int> fTextureIndices; PODArray<int> fSamplerIndices);
COMMAND(SetScissor, SkIRect fScissor);
COMMAND(Draw, PrimitiveType fType; uint32_t fBaseVertex; uint32_t fVertexCount);
COMMAND(DrawIndexed, PrimitiveType fType; uint32_t fBaseIndex; uint32_t fIndexCount; uint32_t fBaseVertex);
COMMAND(DrawInstanced, PrimitiveType fType; uint32_t fBaseVertex; uint32_t fVertexCount; uint32_t fBaseInstance;
    uint32_t fInstanceCount);
COMMAND(DrawIndexedInstanced, PrimitiveType fType; uint32_t fBaseIndex; uint32_t fIndexCount; uint32_t fBaseVertex;
    uint32_t fBaseInstance; uint32_t fInstanceCount);
COMMAND(DrawIndirect, PrimitiveType fType);
COMMAND(DrawIndexedIndirect, PrimitiveType fType);

#undef COMMAND

#define ASSERT_TRIV_DES(T) static_assert(std::is_trivially_destructible<T>::value);
SKGPU_DRAW_PASS_COMMAND_TYPES(ASSERT_TRIV_DES)
#undef ASSERT_TRIV_DES
#define ASSERT_TRIV_CPY(T) static_assert(std::is_trivially_copyable<T>::value);
SKGPU_DRAW_PASS_COMMAND_TYPES(ASSERT_TRIV_CPY)
#undef ASSERT_TRIV_CPY

class List {
public:
    List() = default;
    ~List() = default;

    int count() const
    {
        return fCommands.count();
    }

    void bindGraphicsPipeline(uint32_t pipelineIndex)
    {
        this->add<BindGraphicsPipeline>(pipelineIndex);
    }

    void setBlendConstants(std::array<float, 4> blendConstants)
    {
        this->add<SetBlendConstants>(this->copy(blendConstants.data(), 4));
    }

    void bindUniformBuffer(BindUniformBufferInfo info, UniformSlot slot)
    {
        this->add<BindUniformBuffer>(info, slot);
    }

    // Caller must write 'numTexSamplers' texture and sampler indices into the two returned arrays.
    std::pair<int *, int *> bindDeferredTexturesAndSamplers(int numTexSamplers)
    {
        int *textureIndices = fAlloc.makeArrayDefault<int>(numTexSamplers);
        int *samplerIndices = fAlloc.makeArrayDefault<int>(numTexSamplers);
        this->add<BindTexturesAndSamplers>(numTexSamplers, textureIndices, samplerIndices);
        return { textureIndices, samplerIndices };
    }

    void setScissor(SkIRect scissor)
    {
        this->add<SetScissor>(scissor);
    }

    void bindDrawBuffers(BindBufferInfo vertexAttribs, BindBufferInfo instanceAttribs, BindBufferInfo indices,
        BindBufferInfo indirect)
    {
        this->add<BindDrawBuffers>(vertexAttribs, instanceAttribs, indices, indirect);
    }

    void draw(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount)
    {
        this->add<Draw>(type, baseVertex, vertexCount);
    }

    void drawIndexed(PrimitiveType type, unsigned int baseIndex, unsigned int indexCount, unsigned int baseVertex)
    {
        this->add<DrawIndexed>(type, baseIndex, indexCount, baseVertex);
    }

    void drawInstanced(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount, unsigned int baseInstance,
        unsigned int instanceCount)
    {
        this->add<DrawInstanced>(type, baseVertex, vertexCount, baseInstance, instanceCount);
    }

    void drawIndexedInstanced(PrimitiveType type, unsigned int baseIndex, unsigned int indexCount,
        unsigned int baseVertex, unsigned int baseInstance, unsigned int instanceCount)
    {
        this->add<DrawIndexedInstanced>(type, baseIndex, indexCount, baseVertex, baseInstance, instanceCount);
    }

    void drawIndirect(PrimitiveType type)
    {
        this->add<DrawIndirect>(type);
    }

    void drawIndexedIndirect(PrimitiveType type)
    {
        this->add<DrawIndexedIndirect>(type);
    }

    using Command = std::pair<Type, void *>;
    using Iter = SkTBlockList<Command, 16>::CIter;
    Iter commands() const
    {
        return fCommands.items();
    }

private:
    template <typename T, typename... Args> void add(Args &&... args)
    {
        T *cmd = fAlloc.make<T>(T{ std::forward<Args>(args)... });
        fCommands.push_back(std::make_pair(T::kType, cmd));
    }

    // This copy() is for arrays.
    // It will work with POD only arrays.
    template <typename T> T *copy(const T src[], size_t count)
    {
        static_assert(std::is_trivially_copyable<T>::value);
        T *dst = fAlloc.makeArrayDefault<T>(count);
        memcpy(dst, src, count * sizeof(T));
        return dst;
    }

    SkTBlockList<Command, 16> fCommands{ SkBlockAllocator::GrowthPolicy::kFibonacci };

    // fAlloc needs to be a data structure which can append variable length data in contiguous
    // chunks, returning a stable handle to that data for later retrieval.
    SkArenaAlloc fAlloc{ 256 };
};
} // namespace DrawPassCommands
} // namespace skgpu::graphite

#endif // skgpu_graphite_DrawPassCommands_DEFINED
